#pragma once

#include "data.hpp"
#include "httplib.h"
#include <time.h>

extern cloud::DataManager *data; // extern声明

namespace cloud
{
    // 服务端业务处理类：
    // 搭建HTTP服务器，进行业务处理(借助第三方库：httplib)
    class Service
    {
    private:
        // 业务处理函数：针对客户端的不同请求，进行不同的处理，并返回处理后的响应
        // httplib::Request为httplib库中的请求类
        // httplib::Response为httplib库中的响应类

        // 文件上传请求的业务处理函数（静态成员函数，否则this指针会抢占第一个参数位置）
        static void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            // 使用Request::has_file接口查找用户请求中是否包含上传的文件
            // "file"是客户端与服务端之间约定的字段名称，用于标识上传的文件
            bool ret = req.has_file("file"); // 能查找到，则说明请求中包含用户上传的文件
            if (ret == false)
            {
                rsp.status = 400; // 设置响应的状态码：4XX，客户端错误状态码（服务器无法处理该请求）
                return;
            }

            // 获取用户文件
            const auto &file = req.get_file_value("file"); // const引用延长匿名对象的生命周期
            // get_file_value返回的是MultipartFormData类型的匿名对象
            //  struct MultipartFormData {
            //      std::string name;    标识上传的文件
            //      std::string content; 文件内容
            //      std::string filename;文件名
            //      std::string content_type;内容类型
            //    };

            // 获取文件之后我们就要把它存储到服务端的备份文件目录下
            std::string back_dir = Config::GetInstance()->GetBackDir();            // 获取备份文件目录
            std::string real_path = back_dir + FileUtil(file.filename).FileName(); // 该文件的实际备份存储路径

            FileUtil fu(real_path);
            fu.SetContent(file.content); // 使用FileUtil类的SetContent接口将获取到的文件数据写入文件中

            // 备份完成，将文件信息存储起来
            BackupInfo info;
            info.InitBackupInfo(real_path); // 初始化文件信息
            data->Insert(info);             // 插入存储备份信息的哈希表中

            rsp.status = 200; // 2XX为成功状态码（请求正常处理完毕）
        }

        // 时间戳转时间字符串
        static std::string TimeToStr(time_t time)
        {
            // char* ctime (const time_t * timer);接口即可完成
            std::string ret = ctime(&time);
            return ret;
        }

        // 展示备份文件信息页面请求的业务处理函数（返回给用户一个HTML页面）
        static void ListShow(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1.获取所有的已备份文件信息
            // 服务器启动，所有的备份文件信息就会加载到内存中（哈希表），我们调用数据管理类的相关接口获取即可
            // bool GetAll(std::vector<BackupInfo> &v)
            std::vector<BackupInfo> v;
            data->GetAll(v);

            /*
            // 2.组织形成HTML文件（自己写的，有点丑）
            std::stringstream s;
            s << "<meta charset='UTF-8'>";                     // 告诉浏览器以UTF-8编码来解释数据
            s << "<html><head><title>Download</title></head>"; // 网页的标题为“Download”
            s << "<body><h1>Download</h1><table>";             // 显示为一级标题“Download”
            // 遍历获取所有文件信息形成一个HTML表格
            //<table>：定义整个表格
            // <tr>：定义表格中的一行
            // <td>：定义表格中的一个单元格
            for (auto &info : v)
            {
                s << "<tr>";
                std::string filename = FileUtil(info._real_path).FileName();
                // 第一个单元格设置文件下载链接
                s << "<td><a href='" << info._url_path << "'>" << filename << "</a></td>";
                // 第二个单元格展示文件最后一次访问时间
                // 直接获取的_access_time是一个时间戳，我们要转换成客户看得懂得时间
                s << "<td align='right'>" << TimeToStr(info._access_time) << "</td>"; // align='right'右对齐内容
                // 第三个单元格展示文件大小
                s << "<td align='right'>" << info._file_size << "byte|" << info._file_size / 1024 << "k</td>"; // 1kb=1024byte
                s << "</tr>";
            }
            s << "</table></body></html>";
            */

            // 2.组织形成HTML文件（deepseek美化的）
            std::stringstream s;
            s << "<!DOCTYPE html>"; // 添加HTML5文档类型声明
            s << "<meta charset='UTF-8'>";
            s << "<html><head><title>文件下载中心</title>"; // 修改标题为中文
            s << "<style>";                                 // 添加CSS样式
            s << "  body { font-family: 'Segoe UI', Arial, sans-serif; margin: 40px auto; max-width: 1000px; background-color: #f5f5f5; }";
            s << "  .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }";
            s << "  h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 10px; }";
            s << "  table { width: 100%; border-collapse: collapse; margin-top: 20px; }";
            s << "  th { background-color: #3498db; color: white; padding: 15px; text-align: left; }";
            s << "  td { padding: 12px; border-bottom: 1px solid #ddd; }";
            s << "  tr:hover { background-color: #f8f9fa; }";
            s << "  tr:nth-child(even) { background-color: #f2f2f2; }";
            s << "  a { color: #2980b9; text-decoration: none; font-weight: 500; }";
            s << "  a:hover { color: #3498db; text-decoration: underline; }";
            s << "  .file-size { white-space: nowrap; }";
            s << "  .timestamp { color: #7f8c8d; }";
            s << "</style></head>";
            s << "<body><div class='container'>"; // 添加容器div
            s << "<h1>📁 文件下载中心</h1>";      // 添加emoji图标
            s << "<table>";
            s << "<tr><th>文件名</th><th style='text-align:right;'>最后访问时间</th><th style='text-align:right;'>大小</th></tr>"; // 添加表头

            for (auto &info : v)
            {
                s << "<tr>";
                std::string filename = FileUtil(info._real_path).FileName();

                // 文件名列
                s << "<td><a href='" << info._url_path << "'>📄 " << filename << "</a></td>";

                // 时间列（右对齐）
                s << "<td class='timestamp' style='text-align:right;'>" << TimeToStr(info._access_time) << "</td>";

                // 文件大小列（优化显示）
                s << "<td style='text-align:right;' class='file-size'>";
                if (info._file_size >= 1024 * 1024)
                {
                    s << std::fixed << std::setprecision(1) << (info._file_size / (1024.0 * 1024)) << " MB";
                }
                else if (info._file_size >= 1024)
                {
                    s << std::fixed << std::setprecision(1) << (info._file_size / 1024.0) << " KB";
                }
                else
                {
                    s << info._file_size << " B";
                }
                s << "</td></tr>";
            }

            s << "</table>";
            s << "<div style='margin-top:20px; text-align:center; color:#7f8c8d; font-size:0.9em;'>";
            s << "©智能云备份系统 文件下载服务 | 共 " << v.size() << " 个文件</div>"; // 添加页脚统计
            s << "</div></body></html>";

            // 3.将HTML文本设置到响应的正文/有效载荷中
            rsp.body = s.str();
            // 4.在响应报头中设置有效载荷的类型为html
            // Content-Type 决定了浏览器如何处理响应正文
            // 如果是 text/html，浏览器会将其解析为 HTML 页面并渲染展示
            rsp.set_header("Content-Type", "text/html");
            // 5.设置响应状态码
            rsp.status = 200; // 2XX为成功状态码（请求正常处理完毕）
        }

        // 生成资源的ETag：
        // HTTP协议本身并不关心ETag由什么组成，服务端可自己指定，能唯一标识即可
        // 我们指定ETag为 :  filename-filesize-modifytime
        static std::string GetETag(const BackupInfo &info)
        {
            FileUtil fu(info._real_path);
            std::string etag = fu.FileName();
            etag += "-";
            etag += info._file_size;
            etag += "-";
            etag += info._modify_time;
            return etag;
        }

        // 文件下载请求的业务处理函数
        static void Download(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1.获取用户请求的资源路径：即req.path

            // 2.根据路径，获取对应文件信息
            BackupInfo info;
            data->GetOneByURL(req.path, info);

            // 3.判断该文件是否被压缩，如果是，先解压
            if (info._pack_flag == true)
            {
                FileUtil fu(info._pack_path);   // 注意这里要给是压缩包
                fu.UnCompress(info._real_path); // 解压缩到备份文件目录下

                // 4.删除压缩包，修改压缩标志位（被用户请求了，就变成热点文件了）
                fu.Remove();
                info._pack_flag = false;
                data->Update(info); // 更新文件信息
            }

            // 5.判断用户请求的是否是资源的部分数据（断点续传）
            bool resume = false; // 标识是否需要断点续传（回传部分数据）

            // 当客户端需要下载资源的某部分时，可以通过Range报头指定请求的范围
            // 并附加If-Range报头提供验证信息（该验证信息是服务器在首次响应中提供的，我们这里是ETag）
            if (req.has_header("Range")) // 如果请求报头包含range，则表明客户端请求资源的部分数据
            {
                std::string old_etag = req.get_header_value("If-Range"); // 获取If-Range中的验证信息
                if (old_etag == GetETag(info))
                {
                    resume = true; // 资源没有被修改,则只需回传请求区间的数据
                }
            }

            FileUtil file(info._real_path);
            if (resume == false) // 无需断点续传
            {
                // 读取文件数据放入响应的正文（有效载荷）中
                file.GetContent(rsp.body);

                // 设置响应报头：
                // ETag：唯一标识一个资源，客户端第一次请求该资源，响应报头中会包含ETag，后续再请求该资源，
                // 请求的报头中将包含Etag，服务端判断两个Etag是否匹配，来判断该资源是否被修改过（断点续传也要使用该报头）
                // ，没有修改，客户端则无需重新下载，继续使用本地缓存
                rsp.set_header("ETag", GetETag(info));

                // Accept-Ranges: bytes
                // 告知客户端服务器是否支持范围请求（断点续传），并且数据单位为字节
                rsp.set_header("Accept-Ranges", "bytes");

                // 设置有效载荷类型：二进制数据流
                // 对于该类型，浏览器会下载文件，而不是在窗口中显示内容
                rsp.set_header("Content-Type", "application/octet-stream");

                rsp.status = 200; // 2XX为成功状态码（请求正常处理完毕）
            }
            else // 断点续传，只回传客户端请求区间的数据
            {
                // 这就需要我们获取用户请求报头中Range的区间范围，然后获取指定区间的数据设置响应正文
                // 但是，httplib库内部其实已经实现了对于指定区间数据请求（断点续传）的处理（见apply_ranges接口）
                // 所以，我们只需将文件数据读取到响应正文rsp.body中，它内部会自动根据请求区间，
                // 从body中取出指定区间数据进行响应，并设置了Content-Range报头
                file.GetContent(rsp.body);
                rsp.set_header("ETag", GetETag(info));
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.status = 206; // 请求区间数据返回状态码应该设置206 Partial Content
            }
            // 其实状态码库里面也直接设置了，所以单从实现上来说，这里无需区分是否为断点续传，重点是断点续传思想的理解
        }

    public:
        // 初始化端口、IP和下载前缀
        Service()
        {
            // 使用Config的接口获取配置信息
            Config *instance = Config::GetInstance(); // 获取单例对象
            _server_ip = instance->GetServerIp();
            _server_port = instance->GetServerPort();
            _download_prefix = instance->GetDownloadPrefix();
        };

        // 进行服务端的业务处理
        void RunModule()
        {
            // 1.构建客户端请求与对应处理函数的映射，服务器收到该请求则回调对应的函数

            // 第一个参数为url的请求资源路径，第二个参数为对应的处理函数handler
            // 库中对于handler类型的定义是：void(const Request &, Response &)
            // 需要注意的是，这几个业务处理的函数不能写成成员函数，否则this指针会抢占第一个参数位置

            // 约定文件上传请求方法为post，资源路径为/upload，处理函数为Upload
            _server.Post("/upload", Upload);
            // 约定获取文件展示列表请求方法为Get，资源路径为/listshow，处理函数为ListShow
            _server.Get("/listshow", ListShow);
            // 如果用户Get请求url没有指明要访问的资源路径，默认访问web根目录，返回默认/入口页面（展示备份文件列表）
            _server.Get("/", ListShow);

            // 对于文件下载的请求，有一个问题：
            // 备份的文件有很多个，下载前缀是一样的，但是文件名不一样，怎么办？
            // 这里可以使用正则表达式来进行字符串匹配，从而可以获取到不同的文件
            std::string download_url = _download_prefix + ".*";
            // 解释一下".*"
            //.在正则中可以匹配除“\n”和"\r"之外的任何单个字符，而*可以匹配前面的子表达式任意次
            // 所以".*"就代表可以匹配任意个字符，后续就可以匹配用户请求下载的任何文件

            // 约定下载文件请求方法为Get，资源路径为_download_prefix+文件名，处理函数为ListShow
            _server.Get(download_url, Download);

            // 2.让服务器开始监听来自客户端的请连接求，收到请求就会去回调对应的处理函数
            _server.listen(_server_ip.c_str(), _server_port);
        }

    private:
        int _server_port;             // 服务器端口号
        std::string _server_ip;       // 服务器IP
        std::string _download_prefix; // 文件下载请求的url前缀
        httplib::Server _server;      // 使用httplib库的server类搭建服务器
    };
}